package top.loonglove.utils;

import top.loonglove.status.ElementType;
import top.loonglove.bean.*;

import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.regex.Pattern;

/**
 * @author 向贵龙
 * @date 2021/7/12
 * markdown 标签操作工具类
 */
public final class ElementUtils {
    /**
     * 定义标签正则表达式
     */
    public final static Pattern H1_ELEMENT_PATTERN = Pattern.compile("^# .*");
    public final static Pattern H2_ELEMENT_PATTERN = Pattern.compile("^## .*");
    public final static Pattern H3_ELEMENT_PATTERN = Pattern.compile("^### .*");
    public final static Pattern H4_ELEMENT_PATTERN = Pattern.compile("^#### .*");
    public final static Pattern H5_ELEMENT_PATTERN = Pattern.compile("^##### .*");
    public final static Pattern H6_ELEMENT_PATTERN = Pattern.compile("^###### .*");
    public final static Pattern QUOTE_ELEMENT_PATTERN = Pattern.compile("^> +.*\n$");
    public final static Pattern UNORDERED_LIST_ELEMENT_PATTERN = Pattern.compile("^- .*");
    public final static Pattern TASK_LIST_ELEMENT_PATTERN = Pattern.compile("^- \\[ \\] .*");
    public final static Pattern YAML_ELEMENT_PATTERN = Pattern.compile("^---\n.*\n\n---$");
    public final static Pattern CODE_ELEMENT_PATTERN = Pattern.compile("^(``` .*\n)(.*)(\n```)$");
    public final static Pattern CODE_BLOCK_ELEMENT_PATTERN = Pattern.compile("^`.*`$");
    public final static Pattern BOLD_ELEMENT_PATTERN = Pattern.compile("^\\*\\*.*\\*\\*$");
    public final static Pattern ITALIC_ELEMENT_PATTERN = Pattern.compile("^\\*.*\\*$");
    public final static Pattern UNDERSCORE_ELEMENT_PATTERN = Pattern.compile("^<u>.*</u>$");
    public final static Pattern ORDERED_LIST_ELEMENT_PATTERN = Pattern.compile("^\\d\\. .*$");
    public final static Pattern IMAGE_ELEMENT_PATTERN = Pattern.compile("!\\[.*\\]\\(.*\\)");
    public final static Pattern HYPERLINKS_ELEMENT_PATTERN = Pattern.compile("\\[.*\\]\\(.*\\)");

    /**
     * 创建H1标签
     *
     * @param value 数据
     * @return 标签
     */
    public static AbstractElement createH1(String value) {
        return new SingleElement("#", value, ElementType.H1);
    }

    /**
     * 创建H2标签
     *
     * @param value 数据
     * @return 标签
     */
    public static AbstractElement createH2(String value) {
        return new SingleElement("##", value, ElementType.H2);
    }

    /**
     * 创建H3标签
     *
     * @param value 数据
     * @return 标签
     */
    public static AbstractElement createH3(String value) {
        return new SingleElement("###", value, ElementType.H3);
    }

    /**
     * 创建H4标签
     *
     * @param value 数据
     * @return 标签
     */
    public static AbstractElement createH4(String value) {
        return new SingleElement("####", value, ElementType.H4);
    }

    /**
     * 创建H5标签
     *
     * @param value 数据
     * @return 标签
     */
    public static AbstractElement createH5(String value) {
        return new SingleElement("#####", value, ElementType.H5);
    }

    /**
     * 创建H6标签
     *
     * @param value 数据
     * @return 标签
     */
    public static AbstractElement createH6(String value) {
        return new SingleElement("######", value, ElementType.H6);
    }

    /**
     * 创建引用标签
     *
     * @param value 数据
     * @return 标签
     */
    public static AbstractElement createQuote(String value) {
        return new DoubleElement("> ", value, "\n", ElementType.Quote);
    }

    /**
     * 创建无序列表
     *
     * @param value 无序列表内容
     * @return md元素对象
     */
    public static AbstractElement createUnorderedList(String value) {
        return new SingleElement("-", value, ElementType.UnorderedList);
    }

    /**
     * 创建任务列表
     * @param value 任务列表内容
     * @return 任务列表
     */
    public static AbstractElement createTaskList(String value) {
        return new SingleElement("- [ ]", value, ElementType.TaskList);
    }

    /**
     * 创建YAML格式markdown标签，来源于Typora
     * @param value yaml内容
     * @return yaml标签
     */
    public static AbstractElement createYaml(String value) {
        return new LineDoubleElement("---", value, "\n---", ElementType.Yaml);
    }

    /**
     * 创建代码块
     * @param value 代码块内容
     * @return 代码块标签
     */
    public static AbstractElement createCode(String value) {
        return createCode(value, "");
    }

    /**
     * 指定语言的代码块标签
     * @param value 代码块内容
     * @param language 代码块编程语言
     * @return 代码块标签
     */
    public static AbstractElement createCode(String value, String language) {
        return new LineDoubleElement(String.format("%s %s", "```", language), value, "```", ElementType.Code);
    }

    /**
     * 创建代码小块
     * @param value 代码
     * @return 代码标签
     */
    public static AbstractElement createCodeBlock(String value) {
        return new DoubleElement("`", value, "`", ElementType.CodeBlock);
    }

    /**
     * 创建粗体标签
     * @param value 标签内容
     * @return 粗体标签
     */
    public static AbstractElement createBold(String value) {
        return new DoubleElement("**", value, "**", ElementType.Bold);
    }

    /**
     * 创建斜体标签
     * @param value 标签内容
     * @return 斜体标签
     */
    public static AbstractElement createItalic(String value) {
        return new DoubleElement("*", value, "*", ElementType.Italic);
    }

    /**
     * 创建下划线标签
     * @param value 标签内容
     * @return 下划线标签
     */
    public static AbstractElement createUnderscore(String value) {
        return new DoubleElement("<u>", value, "</u>", ElementType.Underscore);
    }

    /**
     * 创建有序列表
     * @param value 列表内容
     * @return 有序列表
     */
    public static AbstractElement createOrderedList(int num, String value) {
        return new SingleElement(String.format("%d.", num), value, ElementType.OrderedList);
    }


    /**
     * 创建图片标签
     * @param value 图片alt
     * @param url 图片src
     * @return 图片标签
     */
    public static AbstractElement createImage(String value, String url) {
        return new ImageElement(value, url);
    }

    /**
     * 创建超链接标签
     * @param value 标签内容
     * @param url 标签链接地址
     * @return 超链接标签
     */
    public static AbstractElement createHyperlinks(String value, String url) {
        return new HyperlinksElement(value, url);
    }

    /**
     * 文本转换markdown标签
     * @param element 文本内容
     * @return 转换后的标签
     */
    public static AbstractElement stringCovertToElement(String element) {
        AbstractElement res = null;
        try {
            res = covertToH1(element);
            if (Objects.nonNull(res)) {
                return res;
            }
            res = covertToH2(element);
            if (Objects.nonNull(res)) {
                return res;
            }
            res = covertToH3(element);
            if (Objects.nonNull(res)) {
                return res;
            }
            res = covertToH4(element);
            if (Objects.nonNull(res)) {
                return res;
            }
            res = covertToH5(element);
            if (Objects.nonNull(res)) {
                return res;
            }
            res = covertToH6(element);
            if (Objects.nonNull(res)) {
                return res;
            }
            res = covertToQuote(element);
            if (Objects.nonNull(res)) {
                return res;
            }
            res = covertToUnorderedList(element);
            if (Objects.nonNull(res)) {
                return res;
            }
            res = covertToTaskList(element);
            if (Objects.nonNull(res)) {
                return res;
            }
            res = covertToYaml(element);
            if (Objects.nonNull(res)) {
                return res;
            }
            res = covertToCode(element);
            if (Objects.nonNull(res)) {
                return res;
            }
            res = covertToCodeBlock(element);
            if (Objects.nonNull(res)) {
                return res;
            }
            res = covertToBold(element);
            if (Objects.nonNull(res)) {
                return res;
            }
            res = covertToItalic(element);
            if (Objects.nonNull(res)) {
                return res;
            }
            res = covertToUnderscore(element);
            if (Objects.nonNull(res)) {
                return res;
            }
            res = covertToOrderedList(element);
            if (Objects.nonNull(res)) {
                return res;
            }
            res = covertToImage(element);
            if (Objects.nonNull(res)) {
                return res;
            }
            res = covertToHyperlinks(element);
            if (Objects.nonNull(res)) {
                return res;
            }
        } catch (IndexOutOfBoundsException ignored) {

        }

        return new DefaultElement(element,ElementType.Text);
    }

    public static SingleElement covertToH1(String element) {
        if (H1_ELEMENT_PATTERN.matcher(element).matches()) {
            return new SingleElement("#", element.replace("# ", ""), ElementType.H1);
        }
        return null;
    }

    public static SingleElement covertToH2(String element) {
        if (H2_ELEMENT_PATTERN.matcher(element).matches()) {
            return new SingleElement("##", element.replace("## ", ""), ElementType.H2);
        }
        return null;
    }

    public static SingleElement covertToH3(String element) {
        if (H3_ELEMENT_PATTERN.matcher(element).matches()) {
            return new SingleElement("###", element.replace("### ", ""), ElementType.H3);
        }
        return null;
    }

    public static SingleElement covertToH4(String element) {
        if (H4_ELEMENT_PATTERN.matcher(element).matches()) {
            return new SingleElement("####", element.replace("#### ", ""), ElementType.H4);
        }
        return null;
    }

    public static SingleElement covertToH5(String element) {
        if (H5_ELEMENT_PATTERN.matcher(element).matches()) {
            return new SingleElement("#####", element.replace("##### ", ""), ElementType.H5);
        }
        return null;
    }

    public static SingleElement covertToH6(String element) {
        if (H6_ELEMENT_PATTERN.matcher(element).matches()) {
            return new SingleElement("######", element.replace("###### ", ""), ElementType.H6);
        }
        return null;
    }

    public static DoubleElement covertToQuote(String element) {
        if (QUOTE_ELEMENT_PATTERN.matcher(element).matches()) {
            return new DoubleElement("> ", StringUtils.replaceStartAndEnd(element, "> ", "\n"), "\n", ElementType.Quote);
        }
        return null;
    }

    public static SingleElement covertToUnorderedList(String element) {
        if (UNORDERED_LIST_ELEMENT_PATTERN.matcher(element).matches()) {
            return new SingleElement("-", element.replace("- ", ""), ElementType.UnorderedList);
        }
        return null;
    }

    public static SingleElement covertToTaskList(String element) {
        if (TASK_LIST_ELEMENT_PATTERN.matcher(element).matches()) {
            return new SingleElement("- [ ]", element.replaceFirst("- \\[ \\] ", ""), ElementType.TaskList);
        }
        return null;
    }

    public static LineDoubleElement covertToYaml(String element) {
        if (YAML_ELEMENT_PATTERN.matcher(element).matches()) {
            return new LineDoubleElement("---", StringUtils.replaceStartAndEnd(element, "---\n", "\n\n---"), "\n---", ElementType.Yaml);
        }
        return null;

    }

    public static LineDoubleElement covertToCode(String element) {
        if (CODE_ELEMENT_PATTERN.matcher(element).matches()) {
            String[] strings = element.split("\n");
            return new LineDoubleElement(String.format("%s %s", "```", strings[0].replace("``` ", "")), strings[1], "```", ElementType.Code);
        }
        return null;
    }


    public static DoubleElement covertToCodeBlock(String element) {
        if (CODE_BLOCK_ELEMENT_PATTERN.matcher(element).matches()) {
            return new DoubleElement("`", StringUtils.replaceStartAndEnd(element, "`", "`"), "`", ElementType.CodeBlock);
        }
        return null;
    }

    public static DoubleElement covertToBold(String element) {
        if (BOLD_ELEMENT_PATTERN.matcher(element).matches()) {
            return new DoubleElement("**", StringUtils.replaceStartAndEnd(element, "**", "**"), "**", ElementType.Bold);
        }
        return null;
    }

    public static DoubleElement covertToItalic(String element) {
        if (ITALIC_ELEMENT_PATTERN.matcher(element).matches()) {
            return new DoubleElement("*", StringUtils.replaceStartAndEnd(element, "*", "*"), "*", ElementType.Italic);
        }
        return null;
    }

    public static DoubleElement covertToUnderscore(String element) {
        if (UNDERSCORE_ELEMENT_PATTERN.matcher(element).matches()) {
            return new DoubleElement("<u>", StringUtils.replaceStartAndEnd(element, "<u>", "</u>"), "</u>", ElementType.Underscore);
        }
        return null;
    }

    public static SingleElement covertToOrderedList(String element) {
        if (ORDERED_LIST_ELEMENT_PATTERN.matcher(element).matches()) {
            String value = element.replaceFirst("\\d. ", "");
            int num = Integer.parseInt(element.replace(". " + value, ""));
            return new SingleElement(String.format("%d.", num), value, ElementType.OrderedList);
        }
        return null;
    }


    public static ImageElement covertToImage(String element) {

        if (IMAGE_ELEMENT_PATTERN.matcher(element).matches()) {
            String url = StringUtils.replaceStartAndEnd(element.replaceFirst("!\\[.*\\]", ""), "(", ")");
            String value = element.split("]")[0].replace("![", "");
            return new ImageElement(value, url);
        }
        return null;
    }

    public static HyperlinksElement covertToHyperlinks(String element) {
        if (HYPERLINKS_ELEMENT_PATTERN.matcher(element).matches()) {
            String url = StringUtils.replaceStartAndEnd(element.replaceFirst("\\[.*\\]", ""), "(", ")");
            String value = element.split("]")[0].replace("[", "");
            return new HyperlinksElement(value, url);
        }
        return null;
    }

    /**
     * 转换HTML的匹配标签
     */
    private final static HashMap<ElementType, String> htmlElement = new HashMap<ElementType, String>() {{
        put(ElementType.H1, "<h1 class=\"markdown-h1 glkj-h1\">%s</h1>");
        put(ElementType.H2, "<h2 class=\"markdown-h2 glkj-h2\">%s</h2>");
        put(ElementType.H3, "<h3 class=\"markdown-h3 glkj-h3\">%s</h3>");
        put(ElementType.H4, "<h4 class=\"markdown-h4 glkj-h4\">%s</h4>");
        put(ElementType.H5, "<h5 class=\"markdown-h5 glkj-h5\">%s</h5>");
        put(ElementType.H6, "<h6 class=\"markdown-h6 glkj-h6\">%s</h6>");
        put(ElementType.Bold, "<strong class=\"markdown-bold glkj-bold\">%s</strong>");
        put(ElementType.Code, "<pre class=\"markdown-code-pre glkj-code-pre\"><code class=\"markdown-code glkj-code\">%s</code></pre>");
        put(ElementType.Hyperlinks, "<a class=\"markdown-hyperlinks glkj-hyperlinks\" href=\"%s\">%s</a>");
        put(ElementType.CodeBlock, "<code class=\"markdown-code-block glkj-code-block\">%s</code>");
        put(ElementType.Image, "<img class=\"markdown-image glkj-image\" src=\"%s\" alt=\"%s\"/>");
        put(ElementType.Italic, "<em class=\"markdown-italic glkj-italic\">%s</em>");
        put(ElementType.Quote, "<blockquote class=\"markdown-quote glkj-quote\">%s</blockquote>");
        put(ElementType.OrderedList, "<ol class=\"markdown-ordered-list glkj-ordered-list\">%s</ol>");
        put(ElementType.TaskList, "<ul class=\"markdown-task-list glkj-task-list\">%s</ul>");
        put(ElementType.Underscore, "<u class=\"markdown-underscore glkj-underscore\">%s</u>");
        put(ElementType.UnorderedList, "<ul class=\"markdown-unordered-list glkj-unordered-list\">%s</ul>");
        put(ElementType.Yaml, "<hr>%s<hr>");
        put(ElementType.Li, "<li>%s</li>");
        put(ElementType.P, "<p class=\"markdown-block glkj-block\">%s</p>");

    }};

    /**
     * 单个标签转HTML 不换行
     * @param element 标签
     * @return HTML标签
     */
    public static String elementToHtml(AbstractElement element) {
        if (element instanceof HyperlinksElement) {
            HyperlinksElement newElement = (HyperlinksElement) element;
            return String.format(htmlElement.get(newElement.getType()), newElement.getUrl(), newElement.getValue());
        }
        if (element instanceof ImageElement) {
            ImageElement newElement = (ImageElement) element;
            return String.format(htmlElement.get(newElement.getType()), newElement.getUrl(), newElement.getValue());
        }
        switch (element.getType()) {
            case TaskList:
            case UnorderedList:
            case OrderedList:
                return String.format(htmlElement.get(element.getType()),
                        String.format(htmlElement.get(ElementType.Li), element.getValue()));
            default:
                return String.format(htmlElement.get(element.getType()), element.getValue());
        }
    }

    /**
     * 单个标签转HTML 换行
     * @param element 标签
     * @return HTML标签
     */
    public static String elementToHtmlBlock(AbstractElement element) {
        return String.format(htmlElement.get(ElementType.P), elementToHtml(element));
    }

    /**
     * 批量转换标签为HTML
     * @param elements 标签列表
     * @return 换行的HTML文档
     */
    public static String elementsToHtml(List<AbstractElement> elements) {
        AbstractElement preElement = null;
        AbstractElement currentElement = null;
        AbstractElement nextElement = null;
        StringBuilder stringBuffer = new StringBuilder();
        StringBuilder content = new StringBuilder();
        for (int i = 0; i < elements.size(); i++) {
            currentElement = elements.get(i);
            if ((i + 1) < elements.size()) {
                nextElement = elements.get(i + 1);
            }
            if (currentElement.getType() == ElementType.TaskList) {
                content.append(String.format(htmlElement.get(ElementType.Li), currentElement.getValue()));
                if (nextElement == null || currentElement.getType() != nextElement.getType()) {
                    stringBuffer.append(String.format(htmlElement.get(ElementType.P),
                            String.format(htmlElement.get(currentElement.getType()), content.toString())
                            )
                    );
                    content = new StringBuilder();
                    stringBuffer.append("\n");
                }
                continue;
            }
            if (currentElement.getType() == ElementType.UnorderedList) {
                content.append(String.format(htmlElement.get(ElementType.Li), currentElement.getValue()));
                if (nextElement == null || currentElement.getType() != nextElement.getType()) {
                    stringBuffer.append(String.format(htmlElement.get(ElementType.P),
                            String.format(htmlElement.get(currentElement.getType()), content.toString())
                            )
                    );
                    content = new StringBuilder();
                    stringBuffer.append("\n");
                }
                continue;
            }
            if (currentElement.getType() == ElementType.OrderedList) {
                content.append(String.format(htmlElement.get(ElementType.Li), currentElement.getValue()));
                if (nextElement == null || currentElement.getType() != nextElement.getType()) {
                    stringBuffer.append(String.format(htmlElement.get(ElementType.P),
                            String.format(htmlElement.get(currentElement.getType()), content.toString())
                            )
                    );
                    content = new StringBuilder();
                    stringBuffer.append("\n");
                }
                continue;
            }
            stringBuffer.append("\n");
            stringBuffer.append(elementToHtmlBlock(currentElement));
            preElement = currentElement;
        }
        return stringBuffer.toString();
    }

    /**
     * 将markdown文件转换成HTML标签
     * @param file markdown文件
     * @return 文件转换后的HTML标签
     * @throws IOException 文件解析异常
     */
    public static String covertToHtml(File file) throws IOException {
        return elementsToHtml(fileCovertToElements(file));
    }

    /**
     * 将文件解析成markdown标签
     * @param file markdown文件
     * @return markdown标签列表
     * @throws IOException 文件解析异常
     */
    public static List<AbstractElement> fileCovertToElements(File file) throws IOException {
        List<AbstractElement> list = new ArrayList<>();
        try (FileReader fr = new FileReader(file)) {
            try (BufferedReader reader = new BufferedReader(fr)) {
                /*
                 * 先找出所有有开头和结尾的标签
                 */
                String[] elements = reader.lines().toArray(String[]::new);

                boolean isDouble = false;//标志特殊标签开始和结束走改分支
                StringBuilder concatElement = new StringBuilder();

                for (String element : elements) {
                    if (element.startsWith(">")) {
                        AbstractElement abstractElement = stringCovertToElement(element + "\n");
                        if (abstractElement != null) {
                            list.add(abstractElement);
                            continue;
                        }
                    } else if (element.startsWith("---")) {
                        if (isDouble) {
                            //标记双头标签结束
                            isDouble = false;
                            element = "\n\n" + element;
                        } else {
                            //标记双头标签开始
                            concatElement = new StringBuilder();
                            isDouble = true;
                            element = element + "\n";
                        }
                    } else if (element.startsWith("```")) {
                        if (isDouble) {
                            //标记双头标签结束
                            isDouble = false;
                            element = "\n" + element;
                        } else {
                            //标记双头标签开始
                            concatElement = new StringBuilder();
                            isDouble = true;
                            element = element + "\n";
                        }
                    } else {
                        if (!isDouble && concatElement.length() == 0) {
                            if (element.isEmpty() || "\n".equals(element)) {
                                continue;
                            }
                            AbstractElement abstractElement = stringCovertToElement(element);
                            list.add(abstractElement);
                            continue;
                        }
                    }
                    if (isDouble) {
                        concatElement.append(element);
                    } else {
                        concatElement.append(element);
                        AbstractElement abstractElement = stringCovertToElement(concatElement.toString());
                        concatElement = new StringBuilder();
                        if (abstractElement != null) {
                            list.add(abstractElement);
                        }
                    }
                }
                if (isDouble) {
                    AbstractElement abstractElement = stringCovertToElement(concatElement.toString());
                    if (abstractElement != null) {
                        list.add(abstractElement);
                    }
                }
            }
        }
        return list;
    }
}
